using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using WindowsPhoneEssentials.Helpers;
using WindowsPhoneMVC.Navigation;
using WindowsPhoneMVC.Phone.Interfaces;

namespace WindowsPhoneMVC.ActionResults
{
    /// <summary>
    /// Dialog result is returned to display content inside navigation frame
    /// </summary>
    public class PageResult : ViewResult
    {
        private Type modelType;
        private IEnumerable<PropertyInfo> modelProperties;
        private IEnumerable<MethodInfo> modelMethods;

        /// <summary>
        /// Initializes a new instance of the <see cref="PageResult"/> class.
        /// </summary>
        /// <param name="viewName">Name of the view.</param>
        /// <param name="model">The model.</param>
        /// <param name="context">The context.</param>
        public PageResult(string viewName, object model, NavigationContext context)
            : base(viewName, model, context)
        { }

        /// <summary>
        /// Renders view.
        /// </summary>
        /// <param name="navigationFrame">Navigation frame.</param>
        /// <param name="view">View.</param>
        protected override void RenderView(IMvcFrame navigationFrame, UserControl view)
        {
            var page = view as PhoneApplicationPage;
            if (page == null)
                throw new InvalidOperationException("view must be of type PhoneApplicationPage for Page result");

            if (page.ApplicationBar != null)
                BindApplicationBar(page.ApplicationBar);

            navigationFrame.SetupSystemTray(page);
            navigationFrame.SetApplicationBar(page.ApplicationBar);
            navigationFrame.UpdateOrientation(page);
            navigationFrame.Show(page);
        }

        private void BindApplicationBar(IApplicationBar applicationBar)
        {
            foreach (var button in applicationBar.Buttons.OfType<ApplicationBarIconButton>())
            {
                BindNormalApplicationBarItem(button);
            }
            foreach (var menuItem in applicationBar.MenuItems.OfType<ApplicationBarMenuItem>())
            {
                BindNormalApplicationBarItem(menuItem);
            }
        }

        private void BindNormalApplicationBarItem(IApplicationBarMenuItem applicationBarMenuItem)
        {
            if (Model == null)
                return;

            EnsureModelInfoGathered();

            var match = Regex.Match(applicationBarMenuItem.Text, @"(?<Text>.*?)\[(?<Command>.*?)\]");
            if (match.Success)
            {
                applicationBarMenuItem.Text = match.Groups["Text"].Value;
                BindNormalApplicationBarItem(applicationBarMenuItem, match.Groups["Command"].Value);
            }
            else
            {
                BindNormalApplicationBarItem(applicationBarMenuItem, applicationBarMenuItem.Text.Replace(" ", string.Empty));
            }
        }

        private void EnsureModelInfoGathered()
        {
            if (modelType != null) return;

            modelType = Model.GetType();

            modelProperties = modelType
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p=>typeof(ICommand).IsAssignableFrom(p.PropertyType));
            modelMethods = modelType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .Where(m=>m.GetParameters().Length == 0);
        }

        private void BindNormalApplicationBarItem(IApplicationBarMenuItem applicationBarMenuItem, string value)
        {
            var property = modelProperties.FirstOrDefault(k => k.Name == value || k.Name + "Command" == value);

            if (property != null)
            {
                var command = (ICommand)property.GetValue(Model, null);
                if (command == null) return;
                command.CanExecuteChanged += (sender, e) => applicationBarMenuItem.IsEnabled = command.CanExecute(null);
                applicationBarMenuItem.Click += (sender, e) =>
                                                    {
                                                        var focusObj = FocusManager.GetFocusedElement();

                                                        if (focusObj != null && focusObj is FrameworkElement)
                                                        {
                                                            var element = (FrameworkElement)focusObj;
                                                            var focusableElement = element.Ancestors().OfType<Control>().FirstOrDefault();
                                                            if (focusableElement != null)
                                                                focusableElement.Focus();
                                                        }
                                                        Deployment.Current.Dispatcher.BeginInvoke(() => command.Execute(null));
                                                    };
                applicationBarMenuItem.IsEnabled = command.CanExecute(null);
                return;
            }

            var method = modelMethods.FirstOrDefault(k => k.Name == value);
            if (method != null)
            {
                applicationBarMenuItem.Click += (sender, e) => method.Invoke(Model, null);
            }
        }
    }
}